package model;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.image.*;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ResourceBundle;
import javax.imageio.*;
import javax.swing.Icon;
import javax.swing.ImageIcon;

/**
 * Represents an image that supports changing individual pixel colors.
 * 
 * Only the following formats are supported: png, jpg, gif.
 * 
 * @author Robert C. Duvall
 * @author Charlie Hatcher
 */
public class Pixmap {
	private static ResourceBundle myViewSizes = ResourceBundle
			.getBundle("resources.viewSizes");

	private static Dimension DEFAULT_SIZE = new Dimension(Integer
			.parseInt(myViewSizes.getString("CanvasWidth")), Integer
			.parseInt(myViewSizes.getString("CanvasHeight")));
	private static final Color DEFAULT_COLOR = Color.RED;
	private static final String DEFAULT_NAME = "Default";
	private static final String DEFAULT_TYPE = "jpg";

	private Model myModel;
	private String myFileName;
	private BufferedImage myBackgroundImage;
	private Dimension mySize;
	private Collection<Line2D> myBoundaries;

	/**
	 * Create a default pixmap (300x300, completely white)
	 */
	public Pixmap(Model model) {
		this(DEFAULT_SIZE.width, DEFAULT_SIZE.height, DEFAULT_COLOR);
		myModel = model;
	}

	/**
	 * Create a default pixmap with given size
	 */
	public Pixmap(Dimension size) {
		this(size.width, size.height, DEFAULT_COLOR);
	}

	/**
	 * Create a default pixmap with given width and height
	 */
	public Pixmap(int width, int height) {
		this(width, height, DEFAULT_COLOR);
	}

	/**
	 * Create a pixmap with given width and height and filled with given initial
	 * color
	 */
	public Pixmap(int width, int height, Color color) {
		createImage(width, height, color);
		myBoundaries = calculateBoundaries();
	}

	/**
	 * Create a pixmap as a copy of the given pixmap
	 */
	public Pixmap(Pixmap other) {
		myFileName = other.myFileName;
		mySize = other.getSize();
		myBackgroundImage = copyImage(mySize, mySize, other.myBackgroundImage);
	}

	/**
	 * Create a pixmap from the given local file
	 * 
	 * @throws IOException
	 */
	public Pixmap(String fileName) throws IOException {
		if (fileName == null) {
			createImage(DEFAULT_SIZE.width, DEFAULT_SIZE.height, DEFAULT_COLOR);
		} else {
			read(fileName);
		}
	}

	public BufferedImage getBackgroundImage() {
		return myBackgroundImage;
	}

	/**
	 * @return name of this pixmap (without file system information)
	 */
	public String getName() {
		int index = myFileName.lastIndexOf(File.separator);
		if (index >= 0)
			return myFileName.substring(index + 1);
		else
			return myFileName;
	}

	/**
	 * @return true iff the given coordinate is within the bounds of this pixmap
	 */
	public boolean isInBounds(int x, int y) {
		return (0 <= x && x < mySize.width) && (0 <= y && y < mySize.height);
	}

	/**
	 * @return bounds of this pixmap
	 */
	public Dimension getSize() {
		return new Dimension(mySize);
	}

	/**
	 * @return color of the pixel at the given coordinate in this pixmap
	 */
	public Color getColor(int x, int y) {
		if (isInBounds(x, y))
			return new Color(myBackgroundImage.getRGB(x, y));
		else
			return DEFAULT_COLOR;
	}

	/**
	 * Changes the color of the pixel at the given coordinate in this pixmap
	 */
	public void setColor(int x, int y, Color value) {
		if (isInBounds(x, y)) {
			myBackgroundImage.setRGB(x, y, value.getRGB());
		}
	}

	/**
	 * Changes the size of this pixmap
	 * 
	 * Adds default color to fill in blank space if necessary
	 */
	public void setSize(Dimension size) {
		setSize(size.width, size.height);
	}

	/**
	 * Changes the size of this pixmap
	 * 
	 * Adds default color to fill in blank space if necessary
	 */
	public void setSize(int width, int height) {
		if (width != mySize.width || height != mySize.height) {
			Dimension newSize = new Dimension(width, height);
			if (width > mySize.width || height > mySize.height) {
				myBackgroundImage = copyImage(mySize, newSize,
						myBackgroundImage);
			} else {
				// BUGBUG: scale image down instead?
				myBackgroundImage = myBackgroundImage.getSubimage(0, 0, width,
						height);
			}
			mySize = newSize;
		}
	}

	/**
	 * Returns this pixmap as as an Icon to be used in swing.
	 */
	public Icon toIcon() {
		return new ImageIcon(myBackgroundImage);
	}

	/**
	 * Returns this pixmap as as an Icon to be used in swing.
	 */
	public Icon toIcon(int width, int height) {
		return new ImageIcon(myBackgroundImage.getScaledInstance(width, height,
				Image.SCALE_SMOOTH));
	}

	/**
	 * Updates this pixmap to reflect colors from the given local file
	 * 
	 * Note, changes this pixmap's size if necessary
	 * 
	 * @throws IOException
	 */
	public void read(String fileName) throws IOException {
		myFileName = fileName;
		myBackgroundImage = ImageIO.read(new File(myFileName));
		// mySize = new Dimension(myImage.getWidth(), myImage.getHeight());
	}

	/**
	 * Saves this pixmap to the given local file as a JPEG image
	 * 
	 * @throws IOException
	 */
	public void write(String fileName) throws IOException {
		ImageIO.write(myBackgroundImage, DEFAULT_TYPE, new File(fileName));
	}

	public Collection<Line2D> getBoundaries() {
		return myBoundaries;
	}

	private void createImage(int width, int height, Color color) {
		myFileName = DEFAULT_NAME;
		myBackgroundImage = new BufferedImage(width, height,
				BufferedImage.TYPE_INT_ARGB);
		mySize = new Dimension(width, height);

	}

	private BufferedImage copyImage(Dimension from, Dimension to,
			BufferedImage original) {
		int[] data = new int[from.width * from.height];
		BufferedImage result = new BufferedImage(to.width, to.height,
				BufferedImage.TYPE_INT_ARGB);
		original.getRGB(0, 0, from.width, from.height, data, 0, from.width);
		result.setRGB(0, 0, Math.min(from.width, to.width), Math.min(
				from.height, to.height), data, 0, from.width);
		return result;
	}

	private Collection<Line2D> calculateBoundaries() {
		ArrayList<Line2D> allBoundaries = new ArrayList<Line2D>();
		allBoundaries.add(new Line2D.Double(new Point(0, 0), new Point(0,
				mySize.height))); // left
		allBoundaries.add(new Line2D.Double(new Point(mySize.width, 0),
				new Point(mySize.width, mySize.height))); // right
		allBoundaries.add(new Line2D.Double(new Point(0, 0), new Point(
				mySize.width, 0))); // top
		allBoundaries.add(new Line2D.Double(new Point(0, mySize.height),
				new Point(mySize.width, mySize.height))); // bottom
		return allBoundaries;
	}

	/**
	 * Draws this pixmap on the screen.
	 */
	public void paint(Graphics pen) {
		pen.drawImage(myBackgroundImage, 0, 0, mySize.width, mySize.height,
				DEFAULT_COLOR, null);

		for (Turtle currentTurtle : myModel.getAllTurtles()) {
			Dimension turtleSize = currentTurtle.getTurtleImageSize();
			AffineTransform transform = new AffineTransform();
			transform.translate(mySize.getWidth() / 2 - turtleSize.getWidth()
					/ 2, mySize.getHeight() / 2 - turtleSize.getHeight() / 2);
			transform.rotate(Math.toRadians(currentTurtle.getHeading() * -1),
					turtleSize.getWidth() / 2, turtleSize.getHeight() / 2);
			BufferedImage turtleImage = currentTurtle.getTurtleImage();
			AffineTransformOp op = new AffineTransformOp(transform,
					AffineTransformOp.TYPE_BILINEAR);
			turtleImage = op.filter(turtleImage, null);
			// turtleImage = op.filter(turtleImage, null);

			if (currentTurtle.getVisibility()){
			pen.drawImage(turtleImage, (int) (currentTurtle.getLocation()
					.getX() - turtleSize.getWidth() / 2), (int) (currentTurtle
					.getLocation().getY() - turtleSize.getHeight() / 2),
					(int) turtleSize.getWidth(), (int) turtleSize.getHeight(),
					null);
			}
		}
	}

	public void setBackgroundImage(BufferedImage backgroundImage) {
		myBackgroundImage = backgroundImage.getSubimage(0, 0,
				DEFAULT_SIZE.width, DEFAULT_SIZE.height);
		mySize = new Dimension(DEFAULT_SIZE.width, DEFAULT_SIZE.height);
	}
}